﻿Option Strict On

Imports System.ComponentModel

Public Class frmMain

    Private WithEvents bw As BackgroundWorker

    Private Sub frmMain_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
        ' clean up before closing
        My.Settings.lastDriveNumber = lstCDDrive.SelectedIndex
        Bass.BASS_Stop()
        Bass.BASS_Free()

    End Sub

    Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.Icon = My.Resources.Main

        ' Set up audio
        InitBass()
        FindCDDrive()

        ' Load folders
        PopulateTreeview()

        ' apply last settings
        Me.Size = My.Settings.lastMainFormSize
        Me.CenterToScreen()

        If Not String.IsNullOrEmpty(My.Settings.lastFolderSelection) Then
            dlgReadFolder.SelectedPath = My.Settings.lastFolderSelection
            txtFolderName.Text = My.Settings.lastFolderSelection
        End If

        If Not String.IsNullOrEmpty(My.Settings.lastSaveWaveform) Then
            dlgSaveWaveform.InitialDirectory = My.Settings.lastSaveWaveform
        End If

        If Not String.IsNullOrEmpty(My.Settings.LastSaveImage) Then
            dlgSaveImage.InitialDirectory = My.Settings.LastSaveImage
        End If

        If Not String.IsNullOrEmpty(My.Settings.LastTreeNodeSelection) Then
            SelectTreeNode(My.Settings.LastTreeNodeSelection)
        End If

        cmdProcessFolder.Enabled = Not String.IsNullOrEmpty(txtFolderName.Text)
        cmdProcessCD.Enabled = Not IsNothing(lstCDDrive.SelectedItem)

    End Sub

    Private Sub frmMain_ResizeEnd(sender As Object, e As EventArgs) Handles Me.ResizeEnd
        My.Settings.lastMainFormSize = Me.Size

    End Sub

    Private Sub mnuEjectCD_Click(sender As Object, e As EventArgs) Handles mnuEjectCD.Click
        Dim data As New BassFunctions

        data.EjectCD(lstCDDrive.SelectedItem.ToString.Substring(0, 2))

    End Sub

    Private Sub mnuExit_Click(sender As Object, e As EventArgs) Handles mnuExit.Click
        Me.Close()
    End Sub

    Private Sub mnuReprocessWAV_Click(sender As Object, e As EventArgs) Handles mnuReprocessWAV.Click
        If dlgReprocessWAV.ShowDialog = DialogResult.OK Then
            Try
                DisableForm()
                ReprocessWAV(dlgReprocessWAV.FileName)

            Finally
                EnableForm()
            End Try

        End If
    End Sub

    Private Sub mnuBatchFolderProcessing_Click(sender As Object, e As EventArgs) Handles mnuBatchFolderProcessing.Click
        Dim f As frmBatchProcess

        f = New frmBatchProcess
        f.ShowDialog(Me)

    End Sub

    Private Sub mnuGenerateWithOptions_Click(sender As Object, e As EventArgs) Handles mnuGenerateWithOptions.Click
        Dim f As frmGenerateOptions
        Dim files As New List(Of String)

        For Each item As String In lstWaveforms.SelectedItems
            files.Add(IO.Path.Combine(CStr(lstWaveforms.Tag), item & ".wf"))
        Next

        If files.Count = 0 Then Exit Sub

        f = New frmGenerateOptions(files.ToArray)
        f.ShowDialog(Me)

    End Sub

    Private Sub mnuOptions_Click(sender As Object, e As EventArgs) Handles mnuOptions.Click
        Dim f As New frmOptions

        f.ShowDialog(Me)

    End Sub

    Private Sub mnuWebSite_Click(sender As Object, e As EventArgs) Handles mnuWebSite.Click
        Process.Start(My.Settings.WebSiteURL)
    End Sub

    Private Sub mnuAbout_Click(sender As Object, e As EventArgs) Handles mnuAbout.Click
        Dim f As New AboutBox
        f.ShowDialog(Me)
    End Sub

    Private Sub mnuRename_Click(sender As Object, e As EventArgs) Handles mnuRename.Click
        Dim newFilename As String

        If lstWaveforms.SelectedItems.Count <> 1 Then Exit Sub

        newFilename = InputBox("Enter the new waveform file name", "Rename file", lstWaveforms.SelectedItem.ToString)

        If Not String.IsNullOrEmpty(newFilename) Then
            If Not newFilename.EndsWith(".wf", StringComparison.CurrentCultureIgnoreCase) Then newFilename &= ".wf"
            IO.File.Move(IO.Path.Combine(lstWaveforms.Tag.ToString, lstWaveforms.SelectedItem.ToString & ".wf"), IO.Path.Combine(lstWaveforms.Tag.ToString, newFilename))

            LoadWaveforms()

        End If

    End Sub

    Private Sub mnuDelete_Click(sender As Object, e As EventArgs) Handles mnuDelete.Click
        If lstWaveforms.SelectedItems.Count = 0 Then Exit Sub

        If MsgBox($"Are you sure you want to delete {lstWaveforms.SelectedItems.Count} waveform file{IIf(lstWaveforms.SelectedItems.Count = 1, "", "s")}?", MsgBoxStyle.YesNo Or MsgBoxStyle.Question, "Delete files") = MsgBoxResult.Yes Then
            For Each item In lstWaveforms.SelectedItems
                My.Computer.FileSystem.DeleteFile(IO.Path.Combine(lstWaveforms.Tag.ToString, item.ToString & ".wf"), FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
            Next

            LoadWaveforms()

        End If

    End Sub

    Private Sub lstCDDrive_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstCDDrive.SelectedIndexChanged
        cmdProcessCD.Enabled = Not IsNothing(lstCDDrive.SelectedItem)
        cmdTrackSelection.Text = "All Tracks"
        cmdTrackSelection.Tag = Nothing

    End Sub

    Private Sub cmdTrackSelection_Click(sender As Object, e As EventArgs) Handles cmdTrackSelection.Click
        Dim f As frmTracks
        Dim selectedTracks As List(Of Integer)

        If lstCDDrive.SelectedItem Is Nothing Then Exit Sub
        If Not AddOn.Cd.BassCd.BASS_CD_IsReady(lstCDDrive.SelectedIndex) Then Exit Sub

        f = New frmTracks(lstCDDrive.SelectedIndex)
        If f.ShowDialog(Me) = vbOK Then
            selectedTracks = New List(Of Integer)
            For Each s As String In f.lstTracks.SelectedItems
                selectedTracks.Add(CInt(System.Text.RegularExpressions.Regex.Match(s, "(\d+)$").Groups(1).Value))
            Next

            cmdTrackSelection.Text = $"{selectedTracks.Count} Tracks"
            cmdTrackSelection.Tag = selectedTracks

        End If

    End Sub

    Private Sub cmdProcessCD_Click(sender As Object, e As EventArgs) Handles cmdProcessCD.Click
        If lstCDDrive.SelectedItem Is Nothing Then Exit Sub
        If Not AddOn.Cd.BassCd.BASS_CD_IsReady(lstCDDrive.SelectedIndex) Then Exit Sub

        ReadCDBackground()

    End Sub

    Private Sub cmdBrowseFolder_Click(sender As Object, e As EventArgs) Handles cmdBrowseFolder.Click, txtFolderName.Click
        ' Choose folder for reading
        If dlgReadFolder.ShowDialog = DialogResult.OK Then
            txtFolderName.Text = dlgReadFolder.SelectedPath
            My.Settings.lastFolderSelection = dlgReadFolder.SelectedPath
        End If

        cmdProcessFolder.Enabled = Not String.IsNullOrEmpty(txtFolderName.Text)

    End Sub

    Private Sub cmdProcessFolder_Click(sender As Object, e As EventArgs) Handles cmdProcessFolder.Click
        ReadFolderBackground()

    End Sub

    Private Sub tvFolders_BeforeExpand(sender As Object, e As TreeViewCancelEventArgs) Handles tvFolders.BeforeExpand

        ' Folders that have known child folders and pre-populated with an empty node and will lazy load the contents on first expansion
        If e.Node.Nodes.Count = 1 AndAlso e.Node.Nodes(0).Text = "" Then
            e.Node.Nodes.Clear()
            Try
                PopulateSubnode(DirectCast(e.Node.Tag, IO.DirectoryInfo).GetDirectories, e.Node)

            Catch ex As Exception
                e.Cancel = True
                Exit Sub

            End Try

        End If

    End Sub

    Private Sub tvFolders_AfterSelect(sender As Object, e As TreeViewEventArgs) Handles tvFolders.AfterSelect
        Try
            ' Load the waveforms for the selected directory
            LoadWaveforms(DirectCast(e.Node.Tag, IO.DirectoryInfo).FullName)

        Catch ex As Exception

        End Try

    End Sub

    Private Sub lstWaveforms_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstWaveforms.SelectedIndexChanged
        cmdRenderWaveform.Enabled = lstWaveforms.SelectedIndices.Count > 0 AndAlso lstWaveforms.SelectedIndices.Count <= 4
        mnuGenerateWithOptions.Enabled = lstWaveforms.SelectedIndices.Count > 0 AndAlso lstWaveforms.SelectedIndices.Count <= 4
    End Sub

    Private Sub lstWaveforms_KeyUp(sender As Object, e As KeyEventArgs) Handles lstWaveforms.KeyUp
        If e.KeyCode = Keys.Delete Then mnuDelete_Click(sender, EventArgs.Empty)
        If e.KeyCode = Keys.F2 Then mnuRename_Click(sender, EventArgs.Empty)

    End Sub

    Private Sub cmdRenderWaveform_Click(sender As Object, e As EventArgs) Handles cmdRenderWaveform.Click
        Dim files As New List(Of String)

        ' Collect multiple selections
        For Each item As String In lstWaveforms.SelectedItems
            files.Add(IO.Path.Combine(CStr(lstWaveforms.Tag), item & ".wf"))
        Next

        If files.Count = 0 Then Exit Sub

        ' Prompt for image file name to save
        If dlgSaveImage.ShowDialog = DialogResult.OK Then
            My.Settings.LastSaveImage = dlgSaveImage.FileName
            BassFunctions.CreateWaveformImage(files.ToArray, dlgSaveImage.FileName)
            Process.Start(dlgSaveImage.FileName)

        End If

    End Sub

    Private Sub bw_DoWorkCD(sender As Object, e As DoWorkEventArgs)
        Dim data As BassFunctions
        Dim args As DoWorkCDArgs

        args = DirectCast(e.Argument, DoWorkCDArgs)
        data = New BassFunctions
        data.Filename = args.SavePath

        Try
            AddHandler data.TracksRead, AddressOf TracksReadHandler
            AddHandler data.TrackIncrement, AddressOf TrackIncrementHandler

            data.ReadTracksFromCD(args.DriveIndex, args.TrackSequence)

        Finally
            RemoveHandler data.TracksRead, AddressOf TracksReadHandler
            RemoveHandler data.TrackIncrement, AddressOf TrackIncrementHandler

        End Try

        If My.Settings.EjectCDAfterProcessing Then
            data.EjectCD(args.DriveLetter)
        End If

        e.Result = data

    End Sub

    Private Sub bw_DoWorkFolder(sender As Object, e As DoWorkEventArgs)
        Dim data As BassFunctions
        Dim args() As Object

        args = DirectCast(e.Argument, Object())
        data = New BassFunctions
        data.Filename = CStr(args(1))

        Try
            AddHandler data.TracksRead, AddressOf TracksReadHandler
            AddHandler data.TrackIncrement, AddressOf TrackIncrementHandler

            data.ReadTracksFromFolder(CStr(args(0)))

        Finally
            RemoveHandler data.TracksRead, AddressOf TracksReadHandler
            RemoveHandler data.TrackIncrement, AddressOf TrackIncrementHandler

        End Try

        e.Result = data

    End Sub

    Private Sub bw_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles bw.ProgressChanged
        If e.ProgressPercentage = 0 Then
            ' single track increment
            pbReading.Increment(1)

        Else
            ' setting count of tracks in cd/folder
            With pbReading
                If .Value > e.ProgressPercentage Then .Value = e.ProgressPercentage
                .Maximum = e.ProgressPercentage + 1
            End With

        End If

    End Sub

    Private Sub bw_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles bw.RunWorkerCompleted
        Dim data As BassFunctions

        Try
            If e.Error IsNot Nothing Then
                MsgBox($"An error occurred while processing: {e.Error.Message}.", vbExclamation Or vbOKOnly, "Error while processing")
                Exit Sub
            End If

            data = DirectCast(e.Result, BassFunctions)

            ' Create the waveform data file
            data.WaveformData = New Misc.WaveForm(data.Filename & ".wav")
            With data
                .WaveformData.RenderStart(False, BASSFlag.BASS_SAMPLE_FLOAT Or BASSFlag.BASS_STREAM_PRESCAN)
                .WaveformData.WaveFormSaveToFile(data.Filename)
                .Save(data.Filename)
            End With

            pbReading.Increment(1)

            ' Clean up wav file
            CleanUpWaveFile(data.Filename)
            pbReading.Value = 0

            ' Refresh list of waveform data files
            LoadWaveforms()

        Finally
            RemoveHandler bw.DoWork, AddressOf bw_DoWorkCD
            RemoveHandler bw.DoWork, AddressOf bw_DoWorkFolder

            EnableForm()

        End Try

    End Sub

    Private Sub InitBass()
        Debug.WriteLine("Init BASS")
        If Not Bass.BASS_Init(1, 44100, BASSInit.BASS_DEVICE_DEFAULT, Me.Handle) Then
            MsgBox("BASS-Init failed!", MsgBoxStyle.Critical Or vbOKOnly, "Startup error")
        End If

        Debug.WriteLine("Start BASS")
        If Not Bass.BASS_Start() Then
            MsgBox("BASS could not be started!", MsgBoxStyle.Critical Or vbOKOnly, "Startup error")
        End If

    End Sub

    Private Sub FindCDDrive()
        Dim drives As AddOn.Cd.BASS_CD_INFO()

        Debug.WriteLine("Get Drives")
        drives = AddOn.Cd.BassCd.BASS_CD_GetInfos()
        lstCDDrive.Items.Clear()

        For i As Integer = 0 To drives.Count - 1
            lstCDDrive.Items.Add($"{drives(i).DriveLetter}: ({drives(i).product})")
        Next

        lstCDDrive.SelectedIndex = My.Settings.lastDriveNumber

    End Sub

    Private Sub PopulateTreeview()
        Dim node As TreeNode

        ' Initially start with all drives in computer.  If a drive has subfolders, add a placeholder node to indicate it can be expanded.
        ' Defer the loading of the subfolders until the node is first expanded
        For Each d As IO.DriveInfo In My.Computer.FileSystem.Drives
            node = New TreeNode(d.ToString)
            node.Name = d.ToString
            node.Tag = d.RootDirectory
            If d.IsReady Then ' Is drive isn't ready, don't try looking for subfolders
                ' If there is at least one subfolder, add a placeholder
                If d.RootDirectory.EnumerateDirectories.GetEnumerator.MoveNext Then
                    node.Nodes.Add("")
                End If

            End If

            tvFolders.Nodes.Add(node)

        Next

    End Sub

    Private Sub PopulateSubnode(folders() As IO.DirectoryInfo, parent As TreeNode)
        Dim node As TreeNode

        ' loop through folders and add to parent folder
        For Each d As IO.DirectoryInfo In folders
            node = New TreeNode(d.ToString)
            node.Name = d.ToString
            node.Tag = d
            Try
                ' check for subfolders under each, and if anything exists, add a placeholder
                If d.EnumerateDirectories.GetEnumerator.MoveNext Then
                    node.Nodes.Add("")
                End If

            Catch ex As Exception
                ' if there was an error on the first folder read, still add a placeholder in case other folders are accessible
                node.Nodes.Add("")

            End Try

            parent.Nodes.Add(node)

        Next

    End Sub

    Private Sub SelectTreeNode(selectedPath As String)
        Dim path As IO.DirectoryInfo
        Dim tree As New Stack(Of String)
        Dim searchNodes() As TreeNode

        ' get the folder to auto-select in the tree view
        path = New IO.DirectoryInfo(selectedPath)

        ' split the folder hierarchy navigating parents back to the root drive
        Do While path.Parent IsNot Nothing
            tree.Push(path.Name)
            path = path.Parent
        Loop

        ' add the root drive
        tree.Push(IO.Path.GetPathRoot(selectedPath))

        ' Now with a stack that starts with the drive down to the final folder, we can navigate forward down the tree.
        ' Remember when the tree was first built, we only had drives and placeholders for subfolders.  We could not find
        ' a subfolder yet because they aren't loaded
        If tree.Count <> 0 Then
            Dim searchKey As String
            Dim lastNode As TreeNode = Nothing

            ' get the drive and find it in the tree
            searchKey = tree.Pop
            searchNodes = tvFolders.Nodes.Find(searchKey, False)

            ' If we found the drive, expand it to load the subfolders
            If searchNodes.Length > 0 Then
                lastNode = searchNodes(0)
                lastNode.Expand()

                ' If our stack has more subfolders, find each subfolder
                Do While tree.Count <> 0
                    searchKey = tree.Pop
                    searchNodes = searchNodes(0).Nodes.Find(searchKey, False)

                    ' If we found the subfolder, expand it and let it load the subfolders
                    If searchNodes.Length <> 0 Then
                        lastNode = searchNodes(0)
                        lastNode.Expand()
                    Else
                        ' The subfolder doesn't exist anymore
                        Exit Do
                    End If

                Loop ' The stack shrinks with each pop.  If we get to the final subfolder, we stop there.

            End If

            ' however far we got, select that node
            If lastNode IsNot Nothing Then
                tvFolders.SelectedNode = lastNode
                lastNode.EnsureVisible()
                tvFolders.Select()
            End If

        End If

    End Sub

    Private Sub LoadWaveforms()
        If tvFolders.SelectedNode IsNot Nothing Then
            LoadWaveforms(DirectCast(tvFolders.SelectedNode.Tag, IO.DirectoryInfo).FullName)
        End If

    End Sub

    Private Sub LoadWaveforms(path As String)
        lstWaveforms.Items.Clear()
        Try
            For Each s As String In IO.Directory.GetFiles(path, "*.wf")
                lstWaveforms.Items.Add(IO.Path.GetFileNameWithoutExtension(s))
            Next

            My.Settings.LastTreeNodeSelection = path
            lstWaveforms.Tag = path

        Catch ex As Exception

        End Try

    End Sub

    Private Sub CleanUpWaveFile(filename As String)
        If Not My.Settings.DoNotDeleteWave Then IO.File.Delete(filename & ".wav")
    End Sub

    Private Sub ReadCDBackground()
        Dim filename As String
        Dim data As New BassFunctions

        With pbReading
            .Value = 0
            .Maximum = 100
            .Increment(1)
        End With

        ' Prompt to save waveform file
        If dlgSaveWaveform.ShowDialog = DialogResult.OK Then
            data.Markers = New List(Of Double)
            filename = dlgSaveWaveform.FileName
            My.Settings.lastSaveWaveform = filename

            DisableForm()

            ' read tracks from CD
            bw = New BackgroundWorker
            bw.WorkerReportsProgress = True
            AddHandler bw.DoWork, AddressOf bw_DoWorkCD

            bw.RunWorkerAsync(New DoWorkCDArgs(lstCDDrive.SelectedIndex, TryCast(cmdTrackSelection.Tag, List(Of Integer)), lstCDDrive.SelectedItem.ToString.Substring(0, 2), filename))

        End If

    End Sub

    Private Sub ReadFolderBackground()
        Dim filename As String
        Dim data As New BassFunctions

        With pbReading
            .Value = 0
            .Maximum = 100
            .Increment(1)
        End With

        ' Prompt to save waveform file
        If dlgSaveWaveform.ShowDialog = DialogResult.OK Then
            data.Markers = New List(Of Double)
            filename = dlgSaveWaveform.FileName
            My.Settings.lastSaveWaveform = filename

            DisableForm()

            ' read tracks from folder
            bw = New BackgroundWorker
            bw.WorkerReportsProgress = True
            AddHandler bw.DoWork, AddressOf bw_DoWorkFolder

            bw.RunWorkerAsync(New Object() {txtFolderName.Text, filename})

        End If

    End Sub

    Private Sub ReprocessWAV(filename As String)
        Dim data As BassFunctions

        ' Create the waveform data file
        data = New BassFunctions
        data.WaveformData = New Misc.WaveForm(filename)
        With data
            .Filename = filename.TrimEnd(".wav".ToCharArray)
            .WaveformData.RenderStart(False, BASSFlag.BASS_SAMPLE_FLOAT Or BASSFlag.BASS_STREAM_PRESCAN)
            .WaveformData.WaveFormSaveToFile(data.Filename)
            .Save(data.Filename)
        End With

    End Sub

    Private Sub DisableForm()
        mnuMain.Enabled = False
        grpRip.Enabled = False
        grpFiles.Enabled = False
        Me.Cursor = Cursors.WaitCursor
        Application.DoEvents()

    End Sub

    Private Sub EnableForm()
        mnuMain.Enabled = True
        grpRip.Enabled = True
        grpFiles.Enabled = True
        Me.Cursor = Cursors.Default
        Application.DoEvents()

    End Sub

    Private Sub TracksReadHandler(tracks As Integer)
        bw.ReportProgress(tracks)

    End Sub

    Private Sub TrackIncrementHandler()
        bw.ReportProgress(0)
    End Sub

    Private Class DoWorkCDArgs
        Public Property DriveIndex As Integer
        Public Property DriveLetter As String
        Public Property TrackSequence As List(Of Integer)
        Public Property SavePath As String

        Public Sub New()

        End Sub

        Public Sub New(index As Integer, tracks As List(Of Integer), letter As String, path As String)
            Me.DriveIndex = index
            Me.TrackSequence = tracks
            Me.DriveLetter = letter
            Me.SavePath = path
        End Sub

    End Class

End Class